using System;
using System.Collections;
using System.Net;
using System.Threading;
using _3DTicTacToe.Core;
using Lidgren.Network;
using Nito.Async;


namespace _3DTicTacToe.Net
{
    sealed class TClient
    {
        private readonly NetClient _client;

        public readonly IPAddress RemoteAddress;
        public readonly int RemotePort;

        private readonly ActionThread _actionThread;
        private readonly Thread _listenThread;
        private readonly Queue _pendingMessages;
        private readonly Queue _pendingEvents;
        private readonly Queue _outgoingMessages;

        public delegate void DataReceivedDlg(string data);
        public delegate void StatusChangedDlg(NetConnectionStatus status);

        public event DataReceivedDlg OnDataReceived;
        public event StatusChangedDlg OnStatusChanged;

        void PopEvent()
        {
            var info = (NetInfoContainer)_pendingEvents.Dequeue();
            OnDataReceived(info.Buffer.ReadString());
        }

        void HandleMessage()
        {
            var info = (NetInfoContainer)_pendingMessages.Dequeue();
            switch (info.Type)
            {
                case NetMessageType.DebugMessage:
                    Log.Add(info.Buffer.ReadString(), "TClient", "DEBUG");
                    break;
                case NetMessageType.StatusChanged:
                    Log.Add("New status: " + _client.Status, "TClient");
                    if (OnStatusChanged != null) OnStatusChanged(_client.Status);
                    break;
                case NetMessageType.Data:
                    _pendingEvents.Enqueue(info);
                    _actionThread.Do(PopEvent);
                    break;
            }
        }

        void ListenLoop()
        {
            while (true)
            {
                Thread.Sleep(30);

                var buffer = _client.CreateBuffer();
                NetMessageType type;

                _client.ReadMessage(buffer, out type);

                var info = new NetInfoContainer(buffer, type);
                _pendingMessages.Enqueue(info);
                _actionThread.Do(HandleMessage);
            }
        }

        void SendMessageA()
        {
            if (_client.Status != NetConnectionStatus.Connected&&_client.Status == NetConnectionStatus.Connecting)
                return;

            var data = (string) _outgoingMessages.Dequeue();
            var buffer = _client.CreateBuffer(data);

            _client.SendMessage(buffer, NetChannel.ReliableInOrder1);

            //Log.Add("Message sent with data: " + data, "TClient");
        }

        public void SendMessage(string data)
        {
            try
            {
                _outgoingMessages.Enqueue(data);
                _actionThread.Do(SendMessageA);
            }
            catch (ObjectDisposedException)
            { }
        }

        public void Shutdown(string reason)
        {
            _client.Shutdown(reason);
            _listenThread.Abort();
            _actionThread.Dispose();
            _client.Dispose();

            Log.Add("Client terminated", "TClient");
        }

        public void Connect()
        {
            _client.Start();
            _client.Connect(new IPEndPoint(RemoteAddress, RemotePort));

            _actionThread.Start();
            _listenThread.Start();

            Log.Add(string.Format("Client connected to {0}:{1}", RemoteAddress, RemotePort), "TClient");
        }

        public TClient(string remoteHost, int port)
        {
            RemoteAddress = NetUtility.Resolve(remoteHost);
            RemotePort = port;

            var conf = new NetConfiguration("3dtictactoe");
            _client = new NetClient(conf);
            _client.SetMessageTypeEnabled(NetMessageType.ConnectionRejected, true);

            _actionThread = new ActionThread();
            _listenThread = new Thread(ListenLoop);
            _pendingMessages = Queue.Synchronized(new Queue());
            _outgoingMessages = Queue.Synchronized(new Queue());
            _pendingEvents = Queue.Synchronized(new Queue());

            Log.Add("Client created", "TClient");
        }
    }
}